using System;

namespace Noftware.Com.Cryptography
{
	/// <summary>
	/// This is the class that creates the different types of keys.
	/// </summary>
	public class Generator
	{
		#region Constructors

		/// <summary>
		/// Default constructor.
		/// </summary>
		public Generator()
		{
			// Create the random number generator
			_random = new Random();
		}

		#endregion Constructors

		#region Public methods

        /// <summary>
        /// Generate a key based on specified type, casing and key length.
        /// </summary>
        /// <param name="settings">Reference to key settings.</param>
        /// <returns>String key.</returns>
        public string Generate(KeySettings settings)
        {
            string output = string.Empty;
            int keyLength = settings.KeyLength;

            switch (settings.KeyType)
            {
                case KeyType.AlphaNumeric:
                    {
                        // Alpha Numeric key
                        if (keyLength > 0)
                        {
                            // Which casing?
                            switch (settings.KeyCase)
                            {
                                case KeyCase.Mixed:
                                    {
                                        // Mixed case
                                        output = this.AlphaNumericMixedCase(keyLength);
                                        break;
                                    }
                                case KeyCase.Lower:
                                    {
                                        // Lower case
                                        output = this.AlphaNumericLowerCase(keyLength);
                                        break;
                                    }
                                case KeyCase.Upper:
                                    {
                                        // Upper case
                                        output = this.AlphaNumericUpperCase(keyLength);
                                        break;
                                    }
                            } // switch (settings.KeyCase)
                        }
                        break;
                    }
                case KeyType.Numeric:
                    {
                        // Numeric Only key
                        if (keyLength > 0)
                        {
                            output = this.Numeric(keyLength).ToLower();
                        }
                        break;
                    }
                case KeyType.Alpha:
                    {
                        // Alpha Only key
                        if (keyLength > 0)
                        {
                            // Which casing?
                            switch (settings.KeyCase)
                            {
                                case KeyCase.Mixed:
                                    {
                                        // Mixed case
                                        output = this.AlphaMixedCase(keyLength);
                                        break;
                                    }
                                case KeyCase.Lower:
                                    {
                                        // Lower case
                                        output = this.AlphaLowerCase(keyLength);
                                        break;
                                    }
                                case KeyCase.Upper:
                                    {
                                        // Upper case
                                        output = this.AlphaUpperCase(keyLength);
                                        break;
                                    }
                            } // switch (settings.KeyCase)
                        }
                        break;
                    }
                case KeyType.Guid:
                    {
                        // Which casing?
                        switch (settings.KeyCase)
                        {
                            case KeyCase.Mixed:
                                {
                                    // Mixed case
                                    output = this.GuidMixedCase();
                                    break;
                                }
                            case KeyCase.Lower:
                                {
                                    // Lower case
                                    output = this.GuidLowerCase();
                                    break;
                                }
                            case KeyCase.Upper:
                                {
                                    // Upper case
                                    output = this.GuidUpperCase();
                                    break;
                                }
                        } // switch (settings.KeyCase)
                        break;
                    }
            } // switch (settings.KeyType)

            return output;
        }

        /// <summary>
        /// Generate an encrypted or decrypted string.
        /// </summary>
        /// <param name="settings">Package contains details to encrypt or decrypt.</param>
        /// <returns>Decrypted or encrypted string.</returns>
        public string Generate(CryptographySettings settings)
        {
            if (string.IsNullOrWhiteSpace(settings.Input) == true)
            {
                throw new ArgumentException("Input is required.", "CryptographySettings.Input");
            }
            else if (string.IsNullOrWhiteSpace(settings.StringKey) == true && (settings.CryptographyType == CryptographyType.Md5 || settings.CryptographyType == CryptographyType.Rijndael) )
            {
                throw new ArgumentException("String key is required.", "CryptographySettings.StringKey");
            }
            else if (settings.NumericKey == 0 && settings.CryptographyType == CryptographyType.Xor)
            {
                throw new ArgumentException("Numeric key is required.", "CryptographySettings.NumericKey");
            }

            string output;
            Cryptography crypt;
            switch (settings.Mode)
            {
                case Mode.Encryption:
                    {
                        switch (settings.CryptographyType)
                        {
                            case CryptographyType.Rijndael:
                                {
                                    crypt = new Cryptography(settings.StringKey);
                                    output = crypt.RijndaelEncrypt(settings.Input);
                                    break;
                                }
                            case CryptographyType.Md5:
                                {
                                    crypt = new Cryptography(settings.StringKey);
                                    output = crypt.Md5Encrypt(settings.Input);
                                    break;
                                }
                            case CryptographyType.Xor:
                                {
                                    crypt = new Cryptography(settings.NumericKey);
                                    output = crypt.XorString(settings.Input);
                                    break;
                                }
                            case CryptographyType.Hex:
                                {
                                    crypt = new Cryptography();
                                    output = crypt.AsciiToHex(settings.Input);
                                    break;
                                }
                            default:
                                {
                                    // Unknown cryptography type
                                    throw new ArgumentException("Unknown cryptography type.", "CryptographySettings.CryptographyType");
                                }
                        } // switch (package.CryptographyType)

                        break;
                    }
                case Mode.Decryption:
                    {
                        switch (settings.CryptographyType)
                        {
                            case CryptographyType.Rijndael:
                                {
                                    crypt = new Cryptography(settings.StringKey);
                                    output = crypt.RijndaelDecrypt(settings.Input);
                                    break;
                                }
                            case CryptographyType.Md5:
                                {
                                    crypt = new Cryptography(settings.StringKey);
                                    output = crypt.Md5Decrypt(settings.Input);
                                    break;
                                }
                            case CryptographyType.Xor:
                                {
                                    crypt = new Cryptography(settings.NumericKey);
                                    output = crypt.XorString(settings.Input);
                                    break;
                                }
                            case CryptographyType.Hex:
                                {
                                    crypt = new Cryptography();
                                    output = crypt.HexToAscii(settings.Input);
                                    break;
                                }
                            default:
                                {
                                    // Unknown cryptography type
                                    throw new ArgumentException("Unknown cryptography type.", "CryptographySettings.CryptographyType");
                                }
                        } // switch (package.CryptographyType)

                        break;
                    }
                default:
                    {
                        throw new ArgumentException("Unknown cryptography mode.", "CryptographySettings.Mode");
                    }
            }

            return output;
        }

		/// <summary>
		/// Creates a GUID (Global Unique Identifier).
		/// </summary>
		/// <returns type="string">Returns a lower case GUID.</returns>
		public string GuidLowerCase()
		{
			var guid = System.Guid.NewGuid();
			return guid.ToString().ToLower();
		}

		/// <summary>
		/// Creates a GUID (Global Unique Identifier).
		/// </summary>
		/// <returns type="string">Returns an upper case GUID.</returns>
		public string GuidUpperCase()
		{
			return this.GuidLowerCase().ToUpper();
		}

        /// <summary>
        /// Creates a GUID (Global Unique Identifier).
        /// </summary>
        /// <returns type="string">Mixed case GUID key.</returns>
        public string GuidMixedCase()
        {
            // Lowercase GUID
            string guid = this.GuidLowerCase();

            char c;

            // String Builder for the return value
            var key = new System.Text.StringBuilder(guid.Length);

            // What part of the key should be built?
            int part;
            for (int i = 1; i <= guid.Length; i++)
            {
                c = guid[i - 1];
                if (char.IsLetter(c) == true)
                {
                    // Get a number between 1 and 2 (inclusive)
                    part = this.RandomNumberInRange(1, 2);
                    switch (part)
                    {
                        case 1:
                            {
                                // Upper case letter
                                key.Append(c.ToString().ToUpper());
                                break;
                            }
                        case 2:
                            {
                                // Lower case letter
                                key.Append(c.ToString().ToLower());
                                break;
                            }
                    } // switch (part)
                }
                else
                {
                    // Not a letter (it is a number), just append it
                    key.Append(c);
                }
            } // for (int i = 1; i...

            return key.ToString();
        }

		/// <summary>
		/// Returns an upper case alpha key.
		/// </summary>
		/// <param name="keyLength" type="int">Length of the key.</param>
		/// <returns type="string">Upper case alpha key.</returns>
		public string AlphaUpperCase(int keyLength)
		{
			return this.AlphaMixedCase(keyLength).ToUpper();
		}

		/// <summary>
		/// Returns a lower case alpha key.
		/// </summary>
		/// <param name="keyLength" type="int">Length of the key.</param>
		/// <returns type="string">Lower case alpha key.</returns>
		public string AlphaLowerCase(int keyLength)
		{
			return this.AlphaMixedCase(keyLength).ToLower();
		}

		/// <summary>
		/// Returns a mixed case alpha key.
		/// </summary>
		/// <param name="keyLength" type="int">Length of the key.</param>
		/// <returns type="string">Mixed case alpha key.</returns>
		public string AlphaMixedCase(int keyLength)
		{
			// String Builder for the return value
			var key = new System.Text.StringBuilder(keyLength);

			// What part of the key should be built?
			int part;
			for (int i = 1; i <= keyLength; i++)
			{
				// Get a number between 1 and 2 (inclusive)
				part = this.RandomNumberInRange(1, 2);
				switch (part)
				{
					case 1:
					{
						// Upper case letter
						key.Append(this.RandomUppercaseLetter());
						break;
					}
					case 2:
					{
						// Lower case letter
						key.Append(this.RandomLowercaseLetter());
						break;
					}
				} // switch (part)
			} // for (int i = 1; i <= keyLength; i++)

			return key.ToString();
		}

		/// <summary>
		/// Returns an numeric key.
		/// </summary>
		/// <param name="keyLength" type="int">Length of the key.</param>
		/// <returns type="string">Numeric key.</returns>
		public string Numeric(int keyLength)
		{
			// String Builder for the return value
			var key = new System.Text.StringBuilder(keyLength);

			for (int i = 1; i <= keyLength; i++)
			{
				key.Append(this.RandomNumber());
			} // for (int i = 1; i <= keyLength; i++)

			return key.ToString();
		}

		/// <summary>
		/// Returns an alpha numeric upper case key.
		/// </summary>
		/// <param name="keyLength" type="int">Length of the key.</param>
		/// <returns type="string">Alphanumeric upper case key.</returns>
		public string AlphaNumericUpperCase(int keyLength)
		{
			return this.AlphaNumericMixedCase(keyLength).ToUpper();
		}

		/// <summary>
		/// Returns an alpha numeric lower case key.
		/// </summary>
		/// <param name="keyLength" type="int">Length of the key.</param>
		/// <returns type="string">Alphanumeric lower case key.</returns>
		public string AlphaNumericLowerCase(int keyLength)
		{
			return this.AlphaNumericMixedCase(keyLength).ToLower();
		}

		/// <summary>
		/// Returns an alpha numeric mixed case key.
		/// </summary>
		/// <param name="keyLength" type="int">Length of the key.</param>
		/// <returns type="string">Alphanumeric mixed case key.</returns>
		public string AlphaNumericMixedCase(int keyLength)
		{
			// String Builder for the return value
			var key = new System.Text.StringBuilder(keyLength);

			// What part of the key should be built?
			int part;
			for (int i = 1; i <= keyLength; i++)
			{
				// Get a number between 1 and 3 (inclusive)
				part = this.RandomNumberInRange(1, 3);
				switch (part)
				{
					case 1:
					{
						// Upper case letter
						key.Append(this.RandomUppercaseLetter());
						break;
					}
					case 2:
					{
						// Lower case letter
						key.Append(this.RandomLowercaseLetter());
						break;
					}
					case 3:
					{
						// Number
						key.Append(this.RandomNumber());
						break;
					}
				} // switch (part)
			} // for (int i = 1; i <= keyLength; i++)

			return key.ToString();
		}

		#endregion Public methods

		#region Private methods

		/// <summary>
		/// Returns a random (single-digit) uppercase letter.
		/// </summary>
		/// <returns type="string">Random uppercase letter.</returns>
		private string RandomUppercaseLetter()
		{
			return ((char)this.RandomNumberInRange(UPPERCASE_A, UPPERCASE_Z)).ToString();
		}

		/// <summary>
		/// Returns a random (single-digit) lowercase letter.
		/// </summary>
		/// <returns type="string">Random lowercase letter.</returns>
		private string RandomLowercaseLetter()
		{
			return ((char)this.RandomNumberInRange(LOWERCASE_A, LOWERCASE_Z)).ToString();
		}

		/// <summary>
		/// Returns a random (single-digit) number.
		/// </summary>
		/// <returns type="string">Random number.</returns>
		private string RandomNumber()
		{
			return ((char)this.RandomNumberInRange(NUMBER_ZERO, NUMBER_NINE)).ToString();
		}

		/// <summary>
		/// Returns a random number between the specified range values.
		/// </summary>
		/// <param name="startRange" type="int">Start of range (minimum value).</param>
		/// <param name="endRange" type="int">End of range (maximum value).</param>
		/// <returns type="int">A number between specified values.</returns>
		private int RandomNumberInRange(int startRange, int endRange)
		{
			return _random.Next(startRange, endRange + 1);	// Add + 1 to make it inclusive
		}

		#endregion Private methods

		#region Private variables

		/// <summary>
		/// Random number generator object.
		/// </summary>
		private System.Random _random;

		/// <summary>
		/// Uppercase letter "A" in ASCII.
		/// </summary>
		private const int UPPERCASE_A = 65;

		/// <summary>
		/// Uppercase letter "Z" in ASCII.
		/// </summary>
		private const int UPPERCASE_Z = 90;

		/// <summary>
		/// Lowercase letter "a" in ASCII.
		/// </summary>
		private const int LOWERCASE_A = 97;

		/// <summary>
		/// Lowercase letter "z" in ASCII.
		/// </summary>
		private const int LOWERCASE_Z = 122;

		/// <summary>
		/// 0 (zero) in ASCII.
		/// </summary>
		private const int NUMBER_ZERO = 48;

		/// <summary>
		/// 9 (nine) in ASCII.
		/// </summary>
		private const int NUMBER_NINE = 57;

		#endregion Private variables
	}
}
